Skip to content

test(sqlx): eql_v3 SQL-function property tests (CIP-3141)#293

Open
tobyhede wants to merge 5 commits into
eql_v3from
v3-property-tests-fn-doubles
Open

test(sqlx): eql_v3 SQL-function property tests (CIP-3141)#293
tobyhede wants to merge 5 commits into
eql_v3from
v3-property-tests-fn-doubles

Conversation

@tobyhede

@tobyhede tobyhede commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Stacks on #275 (v3-property-tests) and extends its proptest oracle harness with the #290 function-double pattern: call the generated eql_v3.* comparison functions and term extractors by name, across all three operand overloads, asserted against #275's existing plaintext oracle over its live-encryption fixtures. (CIP-3141)

What this adds (over #275's operator oracles)

  1. Named comparison functionseql_v3.eq/neq/lt/lte/gt/gte (and contains/contained_by for bloom), not just the infix operators they back.
  2. All three operand overloads per function — (domain,domain), (domain,jsonb), (jsonb,domain) via Overload::ALL (the jsonb-cast convenience paths test(v3): property-based tests for the eql_v3 encrypted scalar domains (CIP-3141) #275 never reached).
  3. Term-extractor identityeq_term(payload) == hm, ord_term(payload) == ob (re-rendered hex blocks), catalog-driven so text_ord/text_search check both.
  4. Bloom match smoke — curated example-based contains/contained_by (left-contains-right @>) + non-empty match_term over the text fixtures.

Coverage: int4/int2/int8/date (ordered) + timestamptz (eq-only) + text (full eq/ord suite + _search + _match smoke). Tier A (free, fixture corpus) is the bulk; Tier B folds the assertions into the corpus #275 already encrypts per case — ~zero marginal ZeroKMS cost.

Helpers (tests/sqlx/src/property.rs)

Overload, assert_eq_fn_oracle, assert_ord_fn_oracle, assert_extractor_oracle, assert_match_smoke — reusing Row, cast, connect_pool, ensure_fixture_loaded verbatim.

Notes

  • Base-compile fix included (b632175, also pushed to v3-property-tests): the eql_v3 rebase added a token param to Variant::supports_ord, but assert_ord_oracle still called it tokenless, so the eql_tests lib did not compile. Fixed with T::PG_TYPE.
  • ord_term re-render fix: test(eql-types): SQL-backed property tests for int4 v3 domains #290's WITH ORDINALITY AS u(t,n) form expands the single-field composite ore_block_u64_8_256_term into bytea (so (t).bytes fails to resolve); switched to generate_subscripts, verified the re-rendered blocks equal the payload ob.
  • Test-only change — no Cargo.toml/mise.toml/CI changes, no CHANGELOG entry (per CLAUDE.md).

Local verification

test:crates (incl. Tier C) ✅ · test:matrix:inventory ✅ (snapshot unperturbed) · all 36 property:: tests pass against a healthy DB (Tier A + Tier B proptest-live). The combined full test:sqlx run is deferred to CI here (local Docker/disk constraints); this PR exists to run it.

Summary by CodeRabbit

  • New Features

    • Expanded property-based test suite for encrypted scalar domains with function-double oracle validation and match smoke testing.
    • Added end-to-end test coverage for fresh encryption scenarios.
  • Tests

    • Enhanced test infrastructure with new term-extractor identity checks and bloom-filter validation.
    • Improved test documentation and fixture management.
  • Documentation

    • Updated workflow and test documentation for clarity.
    • Enhanced documentation validation to cover source-only checks.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d5da780-e0e5-4204-b5da-064002e874ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3-property-tests-fn-doubles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tobyhede tobyhede marked this pull request as draft June 17, 2026 04:19
@tobyhede tobyhede force-pushed the v3-property-tests branch from 376b4a6 to 2ec34d3 Compare June 18, 2026 01:17
@tobyhede tobyhede force-pushed the v3-property-tests-fn-doubles branch from 572afcc to 3e57268 Compare June 18, 2026 04:24
@tobyhede tobyhede force-pushed the v3-property-tests branch from b41e03b to 7725810 Compare June 18, 2026 07:44
Base automatically changed from v3-property-tests to eql_v3 June 18, 2026 12:34
@tobyhede tobyhede force-pushed the v3-property-tests-fn-doubles branch 2 times, most recently from 5e0d14f to cf3e2d5 Compare June 19, 2026 02:54
@tobyhede tobyhede marked this pull request as ready for review June 19, 2026 02:56
@tobyhede tobyhede force-pushed the v3-property-tests-fn-doubles branch from cf3e2d5 to 65fe909 Compare June 19, 2026 06:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/sqlx/tests/encrypted_domain/property/README.md`:
- Around line 146-147: The cargo test invocation in the README documentation
passes multiple filter arguments separated by spaces, but cargo test only
accepts a single filter string. Fix the command by combining the multiple test
names (fixture_oracle, match_smoke, edge_cases) into a single filter pattern
using regex syntax with the pipe operator, such as combining them into a pattern
that matches all three tests with a single string argument, or alternatively
show multiple separate cargo test invocations if demonstrating individual test
runs is the intent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03f48300-6987-42a1-9143-c4e9c129717e

📥 Commits

Reviewing files that changed from the base of the PR and between 44cfaee and 65fe909.

📒 Files selected for processing (16)
  • .github/workflows/README.md
  • .github/workflows/test-eql.yml
  • CHANGELOG.md
  • crates/eql-tests-macros/src/lib.rs
  • mise.toml
  • tasks/docs/validate/source.sh
  • tests/sqlx/README.md
  • tests/sqlx/src/fixtures/driver.rs
  • tests/sqlx/src/fixtures/scalar_fixture.rs
  • tests/sqlx/src/property.rs
  • tests/sqlx/tests/encrypted_domain/property/README.md
  • tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs
  • tests/sqlx/tests/encrypted_domain/property/edge_cases.rs
  • tests/sqlx/tests/encrypted_domain/property/fixture_oracle.rs
  • tests/sqlx/tests/encrypted_domain/property/match_smoke.rs
  • tests/sqlx/tests/encrypted_domain/property/mod.rs

Comment thread tests/sqlx/tests/encrypted_domain/property/README.md
tobyhede added 3 commits June 19, 2026 23:14
Adds the eql_v3 encrypted-domain property suite over the committed, curated
real-ciphertext fixtures:

- shared all-pairs operator oracle (property.rs): = / <> on _eq and the
  ordered comparisons + ord_term sort order, checked against a plaintext oracle
  over every ordered pair of fixture rows;
- function-double oracles: the generated eql_v3.eq/neq/lt/lte/gt/gte functions
  across all three overloads (domain-domain, domain-jsonb, jsonb-domain) plus
  term-extractor identity (eq_term==hm, ord_term==ob);
- bloom match smoke for the text _match domain; NULL/blocker/CHECK edge cases;
- the e2e suite (gated behind proptest-e2e) over fresh ZeroKMS encryption.

Fixtures are generated from the curated catalog values via FixtureSpec::run().
…P-3141)

Adds a dedicated test:sqlx:e2e mise task and CI job for the proptest-e2e suite
(needs ZeroKMS creds; the credential-free shards run the fixture suite), and
runs source doc validation unconditionally.
Documents the three property-test suites (catalog / fixture / e2e) over the
committed curated fixtures, the function-double oracles, and term-extractor
identity. CHANGELOG entry under [Unreleased].
@tobyhede tobyhede force-pushed the v3-property-tests-fn-doubles branch from 65fe909 to 34d3e9e Compare June 19, 2026 13:16
…le (CIP-3141)

The float4/float8 shared-index-term e2e test pulled both `hm` and `ob`
via `as_str()`, but the ORE `ob` term is a JSON array of block strings,
not a scalar string (only `hm` is a string). `as_str()` returned None on
the array, raising "payload missing string `ob`". Split the helper:
`hm` stays a string, `ob` extracts the array and compares directly,
matching the canonical extractor in property.rs. Latent until the gated
e2e suite started running in CI.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs (2)

253-257: ⚡ Quick win

Strengthen ob extractor to validate block element types, not only array shape.

At Line 253-257, ob currently accepts any JSON array. This can let malformed ORE terms (e.g., non-string elements) pass this test unintentionally.

Proposed patch
-    let ob = |p: &serde_json::Value| -> Result<serde_json::Value> {
-        p.get("ob")
-            .filter(|v| v.is_array())
-            .cloned()
-            .ok_or_else(|| anyhow::anyhow!("payload missing array `ob`: {p}"))
-    };
+    let ob = |p: &serde_json::Value| -> Result<Vec<String>> {
+        let arr = p
+            .get("ob")
+            .and_then(serde_json::Value::as_array)
+            .ok_or_else(|| anyhow::anyhow!("payload missing array `ob`: {p}"))?;
+        arr.iter()
+            .map(|v| {
+                v.as_str()
+                    .map(str::to_owned)
+                    .ok_or_else(|| anyhow::anyhow!("payload `ob` contains non-string block: {p}"))
+            })
+            .collect()
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs` around lines 253 -
257, The `ob` closure at lines 253-257 only validates that the "ob" field is a
JSON array but does not validate the types of elements within that array. To
strengthen this validation, after confirming that the value is an array, add
logic to verify that all elements within the array are of the expected type
(likely strings for ORE block elements). If any element fails type validation,
return an error with a descriptive message indicating which element or type
check failed.

262-270: ⚡ Quick win

Avoid panic-prone [0] indexing in assertions; fail with explicit error context instead.

At Line 263 and Line 269, direct indexing can panic if encrypt_store returns an empty vec, which obscures the root cause during CI failures.

Proposed patch
+    let f4_first = f4_payloads
+        .first()
+        .ok_or_else(|| anyhow::anyhow!("no payload returned for float4 encryption"))?;
+    let f8_first = f8_payloads
+        .first()
+        .ok_or_else(|| anyhow::anyhow!("no payload returned for float8 encryption"))?;
+
     assert_eq!(
-        hm(&f4_payloads[0])?,
-        hm(&f8_payloads[0])?,
+        hm(f4_first)?,
+        hm(f8_first)?,
         "float4 and float8 of the same value must share the hm equality term"
     );
     // ORE term: same f64 input => same ORE ciphertext, so ordering is identical.
     assert_eq!(
-        ob(&f4_payloads[0])?,
-        ob(&f8_payloads[0])?,
+        ob(f4_first)?,
+        ob(f8_first)?,
         "float4 and float8 of the same value must share the ob ORE term"
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs` around lines 262 -
270, The test assertions use unsafe direct indexing with [0] on f4_payloads and
f8_payloads vectors, which will panic if these vectors are empty without
providing any debugging context. Replace the direct indexing [0] accesses with
safer methods like .get(0) or .first() that return Option types, then chain with
.ok_or() or similar error handling to provide explicit, descriptive error
messages when the vectors are unexpectedly empty. Apply this fix to all
instances of f4_payloads[0] and f8_payloads[0] in the assertion blocks to ensure
clear error context if the encryption operations fail to produce results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs`:
- Around line 253-257: The `ob` closure at lines 253-257 only validates that the
"ob" field is a JSON array but does not validate the types of elements within
that array. To strengthen this validation, after confirming that the value is an
array, add logic to verify that all elements within the array are of the
expected type (likely strings for ORE block elements). If any element fails type
validation, return an error with a descriptive message indicating which element
or type check failed.
- Around line 262-270: The test assertions use unsafe direct indexing with [0]
on f4_payloads and f8_payloads vectors, which will panic if these vectors are
empty without providing any debugging context. Replace the direct indexing [0]
accesses with safer methods like .get(0) or .first() that return Option types,
then chain with .ok_or() or similar error handling to provide explicit,
descriptive error messages when the vectors are unexpectedly empty. Apply this
fix to all instances of f4_payloads[0] and f8_payloads[0] in the assertion
blocks to ensure clear error context if the encryption operations fail to
produce results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8fb3117-7b5a-4c08-b9c2-8ba478aca589

📥 Commits

Reviewing files that changed from the base of the PR and between 65fe909 and 5bbd959.

📒 Files selected for processing (16)
  • .github/workflows/README.md
  • .github/workflows/test-eql.yml
  • CHANGELOG.md
  • crates/eql-tests-macros/src/lib.rs
  • mise.toml
  • tasks/docs/validate/source.sh
  • tests/sqlx/README.md
  • tests/sqlx/src/fixtures/driver.rs
  • tests/sqlx/src/fixtures/scalar_fixture.rs
  • tests/sqlx/src/property.rs
  • tests/sqlx/tests/encrypted_domain/property/README.md
  • tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs
  • tests/sqlx/tests/encrypted_domain/property/edge_cases.rs
  • tests/sqlx/tests/encrypted_domain/property/fixture_oracle.rs
  • tests/sqlx/tests/encrypted_domain/property/match_smoke.rs
  • tests/sqlx/tests/encrypted_domain/property/mod.rs
✅ Files skipped from review due to trivial changes (4)
  • tests/sqlx/src/fixtures/scalar_fixture.rs
  • tests/sqlx/README.md
  • mise.toml
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/sqlx/tests/encrypted_domain/property/mod.rs
  • tasks/docs/validate/source.sh
  • crates/eql-tests-macros/src/lib.rs
  • tests/sqlx/tests/encrypted_domain/property/match_smoke.rs
  • tests/sqlx/tests/encrypted_domain/property/edge_cases.rs
  • tests/sqlx/tests/encrypted_domain/property/fixture_oracle.rs
  • tests/sqlx/src/fixtures/driver.rs
  • tests/sqlx/src/property.rs

…or (CIP-3141)

`float4_and_float8_share_index_terms_for_the_same_value` asserted byte-equality
of the raw `ob` ORE arrays of two independently-encrypted payloads. That can
never hold: a BlockORE term is `Left (deterministic) ++ Right (16-byte random
per-ciphertext nonce + nonce-masked truth tables)`, so two encodings of the SAME
value — same width, same cast — are byte-UNEQUAL by construction. Ordering is
decided by the ORE compare function, not raw bytes. (The cast is irrelevant:
`real`/`double` collapse to one f64 `ColumnType::Float` in cipherstash-client;
the deterministic Left halves are byte-identical, which is what proves the two
widths share an encoding.)

The bug stayed latent because the e2e suite is feature/creds-gated and, when it
did run, an earlier `ob`-as-string extraction errored out before the assertion;
fixing that extraction (5bbd959) unmasked the wrong assertion.

Compare the extracted `ord_term`s through the SQL `eql_v3.ore_block_256` `=`
operator (the only correct ORE check) and keep the deterministic `hm` equality
term as a direct byte comparison. Also correct the CHANGELOG claim of a
"byte-identical ORE term" to "equal under the ORE comparator".

Verified: the test now passes against fresh ZeroKMS encryption.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant